package shun.demo.utils;

import android.content.Context;
import shun.demo.AppContext;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by chen on 12/13/13.
 */
public class Singleton {

    private static Policy sPolicy = new ClassPolicy();

    public static <T> T get(Class<T> clazz) {
        if (sPolicy.get(clazz) == null) {
            synchronized (clazz) {
                if (sPolicy.get(clazz) == null) {
                    try {
                        if (clazz.getDeclaredConstructors().length > 1) {
                            throw new RuntimeException("Must have only one private constructor " +
                                    "with Context parameter");
                        }
                        Constructor<T> constructor = clazz.getDeclaredConstructor(Context.class);
                        if (!Modifier.isPrivate(constructor.getModifiers())) {
                            throw new RuntimeException("Must have only one private constructor " +
                                    "with Context parameter");
                        }
                        constructor.setAccessible(true);
                        Object object = constructor.newInstance(AppContext.getInstance());
                        sPolicy.set(clazz, (T) object);
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("Must have only one private constructor" +
                                " with Context parameter", e);
                    }
                }
            }
        }
        return sPolicy.get(clazz);
    }

    public static <T> void destory(Class<T> clazz) {
        sPolicy.set(clazz, null);
    }

    private static abstract class Policy {
        public abstract <T> T get(Class<T> clazz);

        public abstract <T> void set(Class<T> clazz, T object);
    }

    private static class MapPolicy extends Policy {
        // FIXME HashMap不是线程安全的
        private static Map<Class, Object> sInstances = new HashMap<Class, Object>();

        public synchronized <T> T get(Class<T> clazz) {
            return (T) sInstances.get(clazz);
        }

        public synchronized <T> void set(Class<T> clazz, T object) {
            if (object == null) {
                sInstances.remove(clazz);
            } else {
                sInstances.put(clazz, object);
            }
        }
    }

    private static class ClassPolicy extends Policy {

        public <T> T get(Class<T> clazz) {

            try {
                Field field = clazz.getDeclaredField("sInstance");
                field.setAccessible(true);
                Object object = field.get(null);
                return (T) object;
            } catch (NoSuchFieldException e) {
                throw new RuntimeException("Must have a static field named sInstance", e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("This is impossible!", e);
            }
        }

        public <T> void set(Class<T> clazz, T object) {
            try {
                Field field = clazz.getDeclaredField("sInstance");
                field.setAccessible(true);
                field.set(null, object);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException("Must have a static field named sInstance", e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("This is impossible!", e);
            }
        }
    }
}
